//
//  Model.swift
//  Maze24
//
//  Created by Mars on 2020/10/17.
//

import SwiftUI
import Foundation

class Model: ObservableObject {
  @AppStorage("archievements") var archievements: Int = 0
  @AppStorage("level") var level: Int!
  let target = 24
  let epsilon = 1e-6
  
  var question: [Int] = []
  var expressions: [Token] = []
  var histories: [[ButtonItem]] = []
  
  @Published var currentNumBtnIndex: Int = -1
  @Published var currentOpBtnIndex: Int = -1
  
  @Published var buttons: [ButtonItem] = [
    ButtonItem(index: 0, category: .empty),
    ButtonItem(index: 1, category: .empty),
    ButtonItem(index: 2, category: .empty),
    ButtonItem(index: 3, category: .empty)
  ]
  
  @Published var ops: [ButtonItem] = [
    ButtonItem(index: 0, category: .op(.add)),
    ButtonItem(index: 1, category: .op(.sub)),
    ButtonItem(index: 2, category: .op(.mul)),
    ButtonItem(index: 3, category: .op(.div))
  ]
  
  @Published var brain: Brain = .none {
    didSet {
      if oldValue == .none {
        currentOpBtnIndex = -1
      }
    }
  }
  
  @Published var steps: Int = 0
  
  var probability: [Int] = {
    var tmp: [Int] = []
    
    var i = 2;
    
    while (i <= 10) {
      if i == 2 {
        for _ in 0...2 {
          tmp.append(i)
        }
      }
      else if i == 3 {
        for _ in 0...4 {
          tmp.append(i)
        }
      }
      else if i == 4 {
        for _ in 0...6 {
          tmp.append(i)
        }
      }
      else if i == 5 || i == 6 {
        for _ in 0...9 {
          tmp.append(i)
        }
      }
      else if i == 7 {
        for _ in 0...12 {
          tmp.append(i)
        }
      }
      else if i == 8 {
        for _ in 0...14 {
          tmp.append(i)
        }
      }
      else if i == 9 {
        for _ in 0...16 {
          tmp.append(i)
        }
      }
      else if i == 10 {
        for _ in 0...19 {
          tmp.append(i)
        }
      }
      
      i += 1
    }
    
    return tmp
  }()
  
  var isInvalidOp: Bool {
    return brain == .none
  }
  
  var isClearance: Bool {
    let result = steps == 3 && brain.currentValue == 24
    if result {
      update()
    }
    
    return result
  }
  
  var archievement: String {
    let seed = Int.random(in: 0...99)
    updateArchievements(index: probability[seed])
    
    return "smile\(probability[seed])"
  }
  
  func updateArchievements(index: Int) {
    guard index >= 2 && index <= 10 else { return }
    
    let idx = index - 2
    archievements |= (1 << idx)
  }
  
  init() {
    update()
  }
  
  func resetCurrentState() {
    currentNumBtnIndex = -1
    currentOpBtnIndex = -1
    steps = 0
    brain = .none
  }
  
  func update() {
    var set = Set<Int>()
    
    expressions.removeAll(keepingCapacity: true)
    
    while !judgeTarget(Array(set)) {
      set.removeAll()

      while set.count != 4 {
        if level == nil {
          set.insert(Int.random(in: 1..<9))
        }
        else if level == 1 {
          set.insert(Int.random(in: 1..<9))
        }
        else if level == 2 {
          set.insert(Int.random(in: 1..<24))
        }
        else if level == 3 {
          set.insert(Int.random(in: 10..<50))
        }
      }
    }

    let arr = Array(set)
    question = arr
    resetCurrentState()
    
    var i = 0
    buttons = arr.map {
      defer { i += 1 }
      return ButtonItem(index: i, category: .number($0))
    }
  }
  
  func buildAnswer() -> String {
    expressions.removeAll()
    _ = judgeTarget(question) // Update expressions
    return Tree(from: expressions).expression()
  }
  
  func apply(item: ButtonItem) {
    let prevBtnIndex = currentNumBtnIndex
    let shouldMergeButtons = updateButtonIndex(item: item)
    
    brain = brain.apply(item: item)
    
    if shouldMergeButtons {
      mergeButtons(prevButtonIndex: prevBtnIndex, and: item)
    }
  }
  
  /// Whether we could calculate `target` according to the numbers of `nums`.
  /// - Parameters:
  ///   - nums: The numbers that involved in the calculation.
  /// - Returns: `true` if the `target` coule be calculated, else `false`.
  func judgeTarget(_ nums: [Int]) -> Bool {
    let l = nums.map { Double($0) }
    
    return judgeTargetDfs(l)
  }
  
  func revokeOperation() {
    if !histories.isEmpty {
      buttons = histories.popLast()!
      let oldSteps = steps
      
      resetCurrentState()
      
      steps = oldSteps - 1
    }
  }
  
  /**
   * Helper methods
   */
  private func judgeTargetDfs(_ nums: [Double]) -> Bool {
    if nums.isEmpty {
      return false
    }
    
    if nums.count == 1 {
      return fabs(nums[0] - Double(target)) < epsilon
    }
    
    for (i, n) in nums.enumerated() {
      for (j, m) in nums.enumerated() {
        if i != j {
          var theRest: [Double] = []
          
          for (k, x) in nums.enumerated() {
            if (k != i && k != j) {
              theRest.append(x)
            }
          }
          
          for op in Op.allCases {
            var value: Double = 0
            
            if op == .add {
              value = n + m
              theRest.append(value)
            }
            else if op == .sub {
              value = n - m
              theRest.append(value)
            }
            else if op == .mul {
              value = n * m
              theRest.append(value)
            }
            else if op == .div {
              if abs(m) < epsilon { continue }
              value = n / m
              theRest.append(value)
            }
            /// `theRest`
            if judgeTargetDfs(theRest) {
              expressions.append(.operand(Int(n)))
              expressions.append(.operator(op))
              expressions.append(.operand(Int(m)))
              
              return true
            }
            
            theRest.removeLast()
          } /// End `for op in Op.allCases`
        } /// End `if i != j `
      } /// End `for (j, m) in nums.enumerated()`
    } /// End `for (i, n) in nums.enumerated()`
    
    return false
  }
  
  private func mergeButtons(prevButtonIndex: Int, and currButton: ButtonItem) {
    histories.append(buttons)
    
    buttons = buttons.map {
      if $0.index == prevButtonIndex {
        return ButtonItem(index: $0.index, category: .empty)
      }
      
      if $0.index == currButton.index, case Brain.left(let value) = brain {
        return ButtonItem(index: $0.index, category: .number(value))
      }
      
      return $0
    }
  }
  
  private func updateButtonIndex(item: ButtonItem) -> Bool {
    var shouldMergeButtons = false
    
    if case Brain.leftOp(_, _) = brain, item.category.isNumber {
      currentNumBtnIndex = item.index
      currentOpBtnIndex = -1
      steps += 1
      shouldMergeButtons = true
      
      #if DEBUG
      if shouldMergeButtons { print("Shuold merge buttons.") }
      print("Current step: \(steps)")
      #endif
    }
    else if item.category.isNumber {
      currentNumBtnIndex = item.index
    }
    else if item.category.isOp {
      currentOpBtnIndex = item.index
    }
    
    return shouldMergeButtons
  }
}

extension Model {
  var displayBadge1: Bool {
    return archievements == 0x1FF
  }
  
  func displayBadge(of n: Int) -> Bool {
    guard n >= 2 && n <= 10 else { return false }
    return (archievements & (1 << (n - 2))) != 0
  }
}
